Coding Headstart
================

* :download:`Download example <PyObjCExample-Coding Headstart.zip>`

A PyObjC Example without documentation

.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

AppController.py
................

.. sourcecode:: python

    import objc
    from CalendarStore import CalCalendarStore, CalEvent, CalTask
    from Cocoa import NSApp, NSApplication, NSDate, NSLog, NSObject
    
    
    class AppController(NSObject):
        mainWindow = objc.IBOutlet()
        taskCreationDialog = objc.IBOutlet()
        priorityPopup = objc.IBOutlet()
        eventCreationDialog = objc.IBOutlet()
        calendarData = objc.IBOutlet()
    
        calItemTitle = objc.ivar()
        calItemStartDate = objc.ivar()
        calItemEndDate = objc.ivar()
    
        objc.synthesize("calItemTitle", copy=True)
        objc.synthesize("calItemStartDate", copy=True)
        objc.synthesize("calItemEndDate", copy=True)
    
        @objc.IBAction
        def showTaskCreationDialog_(self, sender):
            # Set default values for the title, start date and priority
            # Cocoa bindings will clear out the related fields in the sheet
            self._.calItemTitle = None
            self._.calItemStartDate = NSDate.date()
            NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
                self.taskCreationDialog,
                self.mainWindow,
                self,
                "didEndSheet:returnCode:contextInfo:",
                None,
            )
    
        @objc.IBAction
        def showEventCreationDialog_(self, sender):
            # Set default values for the title and start/end date
            # Cocoa bindings will clear out the related fields in the sheet
            self._.calItemTitle = None
            self._.calItemStartDate = NSDate.date()
            self._.calItemEndDate = NSDate.dateWithTimeIntervalSinceNow_(3600)
            NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
                self.eventCreationDialog,
                self.mainWindow,
                self,
                "didEndSheet:returnCode:contextInfo:",
                None,
            )
    
        # Called when the "Add" button is pressed on the event/task entry sheet
        # This starts the sheet dismissal process
        @objc.IBAction
        def dismissDialog_(self, sender):
            NSApp.endSheet_(sender.window())
    
        @objc.selectorFor(
            NSApplication.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_
        )
        def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
            # Find out which calendar was selected for the new event/task
            # We do this using the calendarData array controller which is bound to
            # the calendar popups in the sheet
            selectedCalendar = None
            count = len(self.calendarData.selectedObjects())
            if count > 0:
                selectedCalendarUID = self.calendarData.selectedObjects()[0].uid()
                selectedCalendar = CalCalendarStore.defaultCalendarStore().calendarWithUID_(
                    selectedCalendarUID
                )
    
            # Create an event/task based on which sheet was used
            if sheet is self.taskCreationDialog:
                if self._.calItemTitle is None:
                    self._.calItemTitle = "My Task"
    
                self.createNewTaskWithCalendar_title_priority_dueDate_(
                    selectedCalendar,
                    self._.calItemTitle,
                    self.priorityPopup.selectedTag(),
                    self._.calItemStartDate,
                )
    
            else:
                if self._.calItemTitle is None:
                    self._.calItemTitle = "My Event"
    
                self.createNewEventWithCalendar_title_startDate_endDate_(
                    selectedCalendar,
                    self._.calItemTitle,
                    self._.calItemStartDate,
                    self._.calItemEndDate,
                )
    
            # Dismiss the sheet
            sheet.orderOut_(self)
    
        def createNewEventWithCalendar_title_startDate_endDate_(
            self, calendar, title, startDate, endDate
        ):
            # Create a new CalEvent object
            newEvent = CalEvent.event()
    
            # Set the calendar, title, start date and end date on the new event
            # using the parameters passed to this method
            newEvent._.calendar = calendar
            newEvent._.title = title
            newEvent._.startDate = startDate
            newEvent._.endDate = endDate
    
            # Save the new event to the calendar store (CalCalendarStore) and
            # return it
            res, err = CalCalendarStore.defaultCalendarStore().saveEvent_span_error_(
                newEvent, 0, None
            )
            if res:
                return newEvent
    
            NSLog("error:%@", err.localizedDescription())
            return None
    
        def createNewTaskWithCalendar_title_priority_dueDate_(
            self, calendar, title, priority, dueDate
        ):
            # Create a new CalTask object
            newTask = CalTask.task()
    
            # Set the calendar, title, priority and due date on the new task
            # using the parameters passed to this method
            newTask._.calendar = calendar
            newTask._.title = title
            newTask._.priority = priority
            newTask._.dueDate = dueDate
    
            # Save the new task to the calendar store (CalCalendarStore) and
            # return it
            res, err = CalCalendarStore.defaultCalendarStore().saveTask_error_(
                newTask, None
            )
            if res:
                return newTask
    
            NSLog("error:%@", err.localizedDescription())
            return None

.. rst-class:: tabbertab

CalController.py
................

.. sourcecode:: python

    """
    Bindings and notification support for Calendar data used
    by this application.  Exposes read-only collections
    (calendars, events, tasks) as observable entities.
    """
    
    from CalendarStore import (
        CalCalendarsChangedExternallyNotification,
        CalCalendarsChangedNotification,
        CalCalendarStore,
        CalEventsChangedExternallyNotification,
        CalEventsChangedNotification,
        CalPriorityHigh,
        CalPriorityMedium,
        CalTasksChangedExternallyNotification,
        CalTasksChangedNotification,
    )
    from Cocoa import NSDate, NSNotificationCenter, NSObject, NSString, NSValueTransformer
    
    highPriority = "High"
    normPriority = "Normal"
    lowPriority = "Low"
    nonePriority = "None"
    
    
    class CalPriorityToStringTransformer(NSValueTransformer):
        """
        The CalPriorityToStringTransformer class allows easy conversion between
        CalPriority values (0-9) and human-readable priority strings (High,
        Normal, Low, None). This allows us to populate the priority dropdown
        using bindings
        """
    
        @classmethod
        def transformedValueClass(cls):
            return type(NSString)
    
        @classmethod
        def allowsReverseTransformation(cls):
            return False
    
        def transformedValue_(self, value):
            priority = value.unsignedIntValue()
            if priority < CalPriorityHigh:
                return nonePriority
    
            elif priority < CalPriorityMedium:
                return highPriority
    
            elif priority == CalPriorityMedium:
                return normPriority
    
            return lowPriority
    
    
    class CalController(NSObject):
        def awakeFromNib(self):
            # Register a transformer object for easy generation of
            # human-readable priority strings
            #
            # See CalPriorityToStringTransformer implementation below
    
            prioTransformer = CalPriorityToStringTransformer.alloc().init()
            NSValueTransformer.setValueTransformer_forName_(
                prioTransformer, "CalPriorityToStringTransformer"
            )
    
            # Register for notifications on calendars, events and tasks so we can
            # update the GUI to reflect any changes beneath us
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "calendarsChanged:", CalCalendarsChangedExternallyNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "calendarsChanged:", CalCalendarsChangedNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "eventsChanged:", CalEventsChangedExternallyNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "eventsChanged:", CalEventsChangedNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "tasksChanged:", CalTasksChangedExternallyNotification, None
            )
    
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
                self, "tasksChanged:", CalTasksChangedNotification, None
            )
    
        # Set up the read-only calendars/events/tasks arrays from Calendar Store
        # as observable keys for Cocoa Bindings
        # This in conjunction with the notifications will allow for immediate UI
        # updates whenever calendar data changes outside of this app
        def calendars(self):
            return CalCalendarStore.defaultCalendarStore().calendars()
    
        def events(self):
            store = CalCalendarStore.defaultCalendarStore()
            # Pull all events starting now from all calendars in the CalendarStore
            allEventsPredicate = (
                CalCalendarStore.eventPredicateWithStartDate_endDate_calendars_(
                    NSDate.date(), NSDate.distantFuture(), store.calendars()
                )
            )
            return store.eventsWithPredicate_(allEventsPredicate)
    
        def tasks(self):
            store = CalCalendarStore.defaultCalendarStore()
            # Pull all uncompleted tasks from all calendars in the CalendarStore
            return store.tasksWithPredicate_(
                CalCalendarStore.taskPredicateWithUncompletedTasks_(store.calendars())
            )
    
        # With the observable keys set up above and the appropriate bindings in IB,
        # we can trigger UI updates just by signaling changes to the keys
        def calendarsChanged_(self, notification):
            self.willChangeValueForKey_("calendars")
            self.didChangeValueForKey_("calendars")
    
        def eventsChanged_(self, notification):
            self.willChangeValueForKey_("events")
            self.didChangeValueForKey_("events")
    
        def tasksChanged_(self, notification):
            self.willChangeValueForKey_("tasks")
            self.didChangeValueForKey_("tasks")

.. rst-class:: tabbertab

main.py
.......

.. sourcecode:: python

    import AppController  # noqa: F401
    import CalController  # noqa: F401
    import objc
    from PyObjCTools import AppHelper
    
    objc.setVerbose(True)
    
    
    AppHelper.runEventLoop()

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    Script for building the example.
    
    Usage:
        python3 setup.py py2app
    """
    
    from setuptools import setup
    
    setup(
        name="PyCalendarStore",
        app=["main.py"],
        data_files=["English.lproj"],
        setup_requires=[
            "py2app",
            "pyobjc-framework-CalendarStore",
            "pyobjc-framework-Cocoa",
        ],
    )

